package com.css.crm.vo;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TreeMap;

import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;



/**
 * Virtual object UserManager to manage users
 */
/*
 * Log			Date			Name					Description
 * 0024746	 	2012 DEC 18		lawrence.ng.c.k			Creation
 */
@ManagedBean(name="userManager")
@SessionScoped
public class UserManager implements Serializable{

	private static final long serialVersionUID = 1L;
	private static final Logger log = Logger.getLogger(UserManager.class);
	
	public static final String BEAN_NAME = "userManager";
	private static final Map<String, UserInfo> currentUserList;
	private static long lastUpdate;

	private static boolean allowMultipleLogin = true;

	static {
		currentUserList = Collections.synchronizedSortedMap(new TreeMap<String, UserInfo>());
		lastUpdate = System.currentTimeMillis();
		log.info("Application|OnStartUp|perform userManager - start");
	}

	/**
	 * Store user's last access item, last view id and last update time<br/>
	 * Update current user list if the current user is not in the list
	 * 
	 * @param user - AbstractUser
	 * @param viewId - view id
	 */
	/*
	 * Log			Date			Name					Description
	 * 0024746	 	2012 DEC 18		lawrence.ng.c.k			Creation
	 */
	public void makeActiveTrack(AbstractUser user, String viewId) {
		if (user != null) {
			String key = user.getId() + user.getSessionId();
			if (!allowMultipleLogin) {
				key = user.getId();
			}
			if (currentUserList.containsKey(key)) {
				UserInfo info = currentUserList.get(key);
				info.setLastAccessTime(new Date());
				info.setLastAccessViewId(viewId);
				UserManager.lastUpdate = System.currentTimeMillis();
			} else if (StringUtils.trimToNull(key) != null) {
				UserInfo info = new UserInfo(user);
				info.setLastAccessTime(new Date());
				info.setLastAccessViewId(viewId);
				currentUserList.put(key, info);
				UserManager.lastUpdate = System.currentTimeMillis();
			}
		}
	}

	/**
	 * Return list of current users
	 * 
	 * @return list of current users
	 */
	/*
	 * Log			Date			Name					Description
	 * 0024746	 	2012 DEC 18		lawrence.ng.c.k			Creation
	 */
	public List<UserInfo> getCurrentUserList() {
		List<UserInfo> info = new ArrayList<UserInfo>();
		Set<String> s = UserManager.currentUserList.keySet();
		
		synchronized(UserManager.currentUserList){
			Iterator<String> i = s.iterator();
			while ( i.hasNext() ){
				UserInfo c = UserManager.currentUserList.get(i.next());
				if (System.currentTimeMillis() - c.getLastAccessTime().getTime() > 10800000) { // 3hrs
					removeActiveTrack(c.getUser());
					log.info("Application|OnService|perform userManager - remove tracking. User: " + c.getUser());
				} else {
					info.add(c);
				}
			}
		}
		return info;
	}
	
	public long getLastUpdate() {
		return UserManager.lastUpdate;
	}

	/**
	 * Remove user from current user list and update last update time
	 * 
	 * @param user - AbstractUser
	 */
	/*
	 * Log			Date			Name					Description
	 * 0024746	 	2012 DEC 18		lawrence.ng.c.k			Creation
	 */
	public void removeActiveTrack(AbstractUser user) {
		String key = user.getId() + user.getSessionId();
		if (!allowMultipleLogin) {
			key = user.getId();
		}
		if (user != null && currentUserList.containsKey(key)) {
			synchronized(UserManager.currentUserList){
				currentUserList.remove(key);
			}
			UserManager.lastUpdate = System.currentTimeMillis();
		}
	}

	/**
	 * Clear cache list and resource entries to refresh resource bundle cache
	 * 
	 * @param event - ActionEvent
	 */
	/*
	 * Log			Date			Name					Description
	 * 0024746	 	2012 DEC 18		lawrence.ng.c.k			Creation
	 */
	public void refreshResourceBundleCache(final ActionEvent event){
		try{
			clearMap(ResourceBundle.class, "cacheList", "clear", null);
			final ClassLoader loader = Thread.currentThread().getContextClassLoader();
			final Class<?> clazz = loader.getClass();
			if("org.apache.catalina.loader.WebappClassLoader".equals(clazz.getName())){
				clearMap(clazz, "resourceEntries", "clear", loader);
			}
			final String msg = "Refreshed ResourceBundle cache.";
			FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, msg, msg));
			log.info(msg);
		}catch(final Exception e){
			final String msg = "Cannot refresh ResourceBundle cache.";
			log.error(msg, e);
			FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg));
		}
	}
	
	/**
	 * Clear map
	 * 
	 * @param clazz - Class<?>
	 * @param fieldName - field name 
	 * @param methodName - method name
	 * @param obj - Object
	 * @throws SecurityException
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException 
	 * @throws IllegalAccessException
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 */
	/*
	 * Log			Date			Name					Description
	 * 0024746	 	2012 DEC 18		lawrence.ng.c.k			Creation
	 */
	private static void clearMap(final Class<?> clazz, final String fieldName, final String methodName, final Object obj) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException{
		final Field field = clazz.getDeclaredField(fieldName);
		field.setAccessible(true);
		final Object cache = field.get(obj);
		final Method method = cache.getClass().getMethod(methodName, (Class[])null);
		method.setAccessible(true);
		method.invoke(cache, (Object[])null);
	}
}